package com.cloudfun.campusshare.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.extra.ssh.JschUtil;
import cn.hutool.extra.ssh.Sftp;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.model.OSSObject;
import com.cloudfun.campusshare.common.constant.AttachType;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.constant.KjtServiceType;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.dto.KjtInvokeHisDTO;
import com.cloudfun.campusshare.common.model.dto.kjt.*;
import com.cloudfun.campusshare.common.model.vo.AttachVO;
import com.cloudfun.campusshare.configuration.property.KjtProperties;
import com.cloudfun.campusshare.entity.AttachmentEntity;
import com.cloudfun.campusshare.util.AliyunOSSUtil;
import com.cloudfun.campusshare.util.DateUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jcraft.jsch.ChannelSftp;
import com.kjtpay.gateway.common.domain.VerifyResult;
import com.kjtpay.gateway.common.domain.base.RequestBase;
import com.kjtpay.gateway.common.domain.base.ResponseParameter;
import com.kjtpay.gateway.common.util.security.SecurityService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 子商户注册、准入资料重提、变更结算银行账户需要人工审核
 * Created by Huqin on 2020/8/3
 */
@Slf4j
@Service
public class KjtService implements InitializingBean {

    @Autowired
    private KjtProperties kjtProperties;

    @Autowired
    private AliyunOSSUtil aliyunOSSUtil;

    @Autowired
    private AttachmentService attachmentService;

    @Autowired
    private KjtInvokeHisService invokeHisService;

    @Autowired
    private Snowflake snowflake;

    @Autowired
    private SecurityService securityService;

    private ObjectMapper objectMapper;

    private RestTemplate restTemplate;

    @Override
    public void afterPropertiesSet() throws Exception {
        // jackson
        objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //  http请求
        restTemplate = new RestTemplate();
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        converterList.set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        for (HttpMessageConverter<?> httpMessageConverter : converterList) {
            if (httpMessageConverter instanceof AbstractHttpMessageConverter) {
                AbstractHttpMessageConverter<?> abstractHttpMessageConverter = (AbstractHttpMessageConverter<?>) httpMessageConverter;
                List<MediaType> supportedMediaTypes = new ArrayList<>(abstractHttpMessageConverter.getSupportedMediaTypes());
                // 拿到JsonConverter
                if (supportedMediaTypes.contains(MediaType.APPLICATION_JSON)) {
                    supportedMediaTypes.add(0, MediaType.TEXT_HTML);
                    // 让其能处理text/plain的数据
                    ((AbstractHttpMessageConverter<?>) httpMessageConverter).setSupportedMediaTypes(supportedMediaTypes);
                }
            }
        }
    }

    public KjtSubMerchantRegPageRespDTO subMerchantRegisterPage(KjtSubMerchantRegPageReqDTO regDTO) {
        regDTO.setNotifyUrl(kjtProperties.getNotifyUrl());
        regDTO.setReturnUrl(kjtProperties.getSubMerchantReturnUrl());
        KjtSubMerchantRegPageRespDTO resp = this.execute(regDTO, KjtServiceType.SUB_MERCHANT_PAGE_REGISTRATION,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantRegPageRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户注册
     * 本接口提供给平台型商户调用，进行子商户在快捷通的注册准入。
     * 注册信息提交后，会生成审核工单，待人工审核。
     * 如审核缺少相关影印件资料可调用【准入资料重提网关接口】将资料重新提交。
     * 人工审核完成后，会有【子商户注册及准入审核结果通知】给平台方，或调用【子商户注册查询网关接口】查询注册及审核状态。
     *
     * @param regDTO
     * @return
     */
    public KjtSubMerchantRegRespDTO subMerchantRegister(KjtSubMerchantRegReqDTO regDTO) {
        regDTO.setNotifyUrl(kjtProperties.getNotifyUrl());
        KjtSubMerchantRegRespDTO resp = this.execute(regDTO, KjtServiceType.SUB_MERCHANT_REGISTRATION,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantRegRespDTO>>() {
                });
        return resp;
    }

    /**
     * 准入资料重提
     * 子商户注册没有通过才能请求此接口
     * 子商户准入审核失败后，可通过此API接口重新提交影印件资料进行开户准入审核
     *
     * @param resubmitInfoDTO
     * @return
     */
    public KjtResubmissionInfoRespDTO resubmissionInformation(KjtResubmissionInfoDTO resubmitInfoDTO) {
        resubmitInfoDTO.setNotifyUrl(kjtProperties.getNotifyUrl());
        KjtResubmissionInfoRespDTO resp = this.execute(resubmitInfoDTO, KjtServiceType.RESUBMISSION_INFORMATION,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtResubmissionInfoRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户注册查询
     * 注册实名认证结果查询接口主要是提供给个人商家快速注册实名认证接口与企业/个体工商户注册实名认证接口配套使用，
     * 供平台调用方主动查询开户与实名认证（准入审核）的结果状态。
     *
     * @param queryDTO
     * @return
     */
    public KjtSubMerchantRegQueryRespDTO subMerchantRegisteredQuery(KjtSubMerchantRegQueryDTO queryDTO) {
        KjtSubMerchantRegQueryRespDTO resp = this.execute(queryDTO, KjtServiceType.SUB_MERCHANT_REGISTERED_QUERY,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantRegQueryRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户结算银行账户变更
     * 子商户注册通过后才能请求次接口
     * 结算银行账户信息变更表，对公卡需要加盖公章，对私卡需要经营者本人手写签字（需要法务与风控确认模板）——已线下提供模板给技术，植入接口文档
     * 平台方按要求提交子商户的变更结算银行卡相关资料后，由风控人工审核，审核通过才能完成结算银行账户的更新，目前只支持大商户平台的子商户绑定一张结算银行卡。
     * 注：通过变更结算银行卡账户，营业执照号、企业名称、身份证号、姓名、开户名等不可修改，
     * 修改的话需通过线下提交相关资料，人工审核通过后进行修改，具体提交资料可联系快捷通客服确认。
     * <p>
     * 存在待审核的工单就不能再次提交
     *
     * @param reqDTO
     * @return
     */
    public KjtSubMerchantChangeBankCardRespDTO subMerchantChangeBankCard(KjtSubMerchantChangeBankCardReqDTO reqDTO) {
        reqDTO.setNotifyUrl(kjtProperties.getNotifyUrl());
        KjtSubMerchantChangeBankCardRespDTO resp = this.execute(reqDTO, KjtServiceType.SUB_MERCHANT_CHANGE_BANKCARD,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantChangeBankCardRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户结算银行账户变更查询
     * 子商户结算银行账户变更查询接口主要是提供给大商户平台查询其下的子商户（个人商家或企业/个体工商户）变更结算银行账户审核的结果状态
     *
     * @param outTradeNo
     * @return
     */
    public KjtSubMerchantChangeBankCardQueryRespDTO subMerchantChangeBankCardQuery(String outTradeNo) {
        KjtSubMerchantChangeBankCardQueryDTO queryDTO = KjtSubMerchantChangeBankCardQueryDTO.builder()
                .outTradeNo(outTradeNo)
                .build();
        KjtSubMerchantChangeBankCardQueryRespDTO resp = this.execute(queryDTO, KjtServiceType.SUB_MERCHANT_CHANGE_BANKCARD_QUERY,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantChangeBankCardQueryRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户结算方式变更
     * 子商户结算方式变更接口主要是提供给大商户平台通过API接口修改其下的子商户（个人商家或企业/个体工商户）变更结算方式的操作。
     * 该接口支持子商户对于资金结算方式可自主选择主动提现或委托提现，如调整为委托提现，还可以对结算周期与起结金额进行选择。
     *
     * @param reqDTO
     * @return
     */
    public KjtSubMerchantChangeSettleTypeRespDTO subMerchantChangeSettleType(KjtSubMerchantChangeSettleTypeReqDTO reqDTO) {
        reqDTO.setNotifyUrl(kjtProperties.getNotifyUrl());
        KjtSubMerchantChangeSettleTypeRespDTO resp = this.execute(reqDTO, KjtServiceType.SUB_MERCHANT_CHANGE_SETTLE_TYPE,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantChangeSettleTypeRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户主动提现到卡
     * 平台方子商户主动选择提现，通过快捷通API接口提现到指定银行卡，支持实时、普通、次日三种时效方式。
     *
     * @param reqDTO
     * @return
     */
    public KjtSubMerchantWithdrawalCashRespDTO subMerchantWithdrawalCash(KjtSubMerchantWithdrawalCashReqDTO reqDTO) {
        reqDTO.setNotifyUrl(kjtProperties.getNotifyUrl());
        KjtSubMerchantWithdrawalCashRespDTO resp = this.execute(reqDTO, KjtServiceType.SUB_MERCHANT_WITHDRAWAL_CASH,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantWithdrawalCashRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户主动提现到户（结算户归集一般户）
     * 结算户到一般户转账，通过快捷通API接口，支持实时时效方式。
     *
     * @param reqDTO
     * @return
     */
    @Deprecated
    public KjtSubMerchantWithdrawToAccountRespDTO subMerchantWithdrawToAccount(KjtSubMerchantWithdrawToAccountReqDTO reqDTO) {
        KjtSubMerchantWithdrawToAccountRespDTO resp = this.execute(reqDTO, KjtServiceType.SUB_MERCHANT_WITHDRAW_TO_ACCOUNT,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantWithdrawToAccountRespDTO>>() {
                });
        return resp;
    }

    /**
     * 主动提现查询
     * 该接口用于查询转账到银行卡的订单状态。
     *
     * @param queryDTO
     * @return
     */
    public KjtTradeQueryRespDTO tradeQuery(KjtTradeQueryDTO queryDTO) {
        KjtTradeQueryRespDTO resp = this.execute(queryDTO, KjtServiceType.TRADE_QUERY,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtTradeQueryRespDTO>>() {
                });
        return resp;
    }

    /**
     * 查询账户余额列表
     * 该接口由商户/平台发起，查询商户/平台自身或者存在签约关系子商户/会员的快捷通账户可用余额及冻结余额。
     *
     * @param reqDTO
     * @return
     */
    public KjtQueryAccountBalanceListRespDTO queryAccountBalanceList(KjtQueryAccountBalanceListReqDTO reqDTO) {
        KjtQueryAccountBalanceListRespDTO resp = this.execute(reqDTO, KjtServiceType.QUERY_ACCOUNT_BALANCE_LIST,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtQueryAccountBalanceListRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户结算银行卡信息查询
     * 该接口由商户/平台发起，查询商户/平台的子商户/会员绑定的结算银行卡信息。
     *
     * @param reqDTO
     * @return
     */
    public KjtQuerySubMerchantBankCardInfoRespDTO querySubMerchantBankCardInfo(KjtQuerySubMerchantBankCardInfoReqDTO reqDTO) {
        KjtQuerySubMerchantBankCardInfoRespDTO resp = this.execute(reqDTO, KjtServiceType.QUERY_SUB_MERCHANT_BANKCARD_INFO,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtQuerySubMerchantBankCardInfoRespDTO>>() {
                });
        return resp;
    }

    /**
     * 申请子商户权限
     * 平台型商家不需要调用此接口
     * <p>
     * 申请子商户权限。只能申请平台方已有的权限，该申请会提交给人工审核。
     * 审核结果会通过【子商户权限审核结果通知】给平台方，也可在【子商户权限申请查询网关接口】查询申请结果。
     *
     * @param reqDTO
     * @return
     */
    @Deprecated
    public KjtSubMerchantPermissionApplyRespDTO subMerchantPermissionApply(KjtSubMerchantPermissionApplyReqDTO reqDTO) {
        KjtSubMerchantPermissionApplyRespDTO resp = this.execute(reqDTO, KjtServiceType.SUB_MERCHANT_PERMISSION_APPLY,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantPermissionApplyRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户权限申请查询
     * 由商户/平台发起，查询商户/平台的子商户/会员申请快捷通权限的审核状态。
     *
     * @param reqDTO
     * @return
     */
    @Deprecated
    public KjtSubMerchantPermissionApplyQueryRespDTO subMerchantPermissionApplyQuery(KjtSubMerchantPermissionApplyQueryReqDTO reqDTO) {
        KjtSubMerchantPermissionApplyQueryRespDTO resp = this.execute(reqDTO, KjtServiceType.SUB_MERCHANT_PERMISSION_APPLY_QUERY,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantPermissionApplyQueryRespDTO>>() {
                });
        return resp;
    }

    /**
     * 子商户支付密码设置
     * 由商户/平台发起，查询商户/平台的子商户/会员一般户支付密码是否设置；
     * 查询返回未设置，由商户引导跳转快捷通提供的首次支付密码设置页面
     *
     * @param reqDTO
     */
    @Deprecated
    public KjtSubMerchantCheckPasswordRespDTO subMerchantCheckPassword(KjtSubMerchantCheckPasswordReqDTO reqDTO) {
        KjtSubMerchantCheckPasswordRespDTO resp = this.execute(reqDTO, KjtServiceType.SUB_MERCHANT_CHECK_PASSWORD,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtSubMerchantCheckPasswordRespDTO>>() {
                });
        return resp;
    }

    /**
     * 扫码支付进件
     * 由平台发起，对平台内扫码收单的子商户进件报备。提交平台内子商户相应扫码支付渠道进件要求的相关信息，供支付渠道方准入审核。
     * 目前是平台方进件报备，不需要子商户再进件
     *
     * @param reqDTO
     * @return
     */
    @Deprecated
    public KjtQrCodeApplyRespDTO qrCodeApply(KjtQrCodeApplyReqDTO reqDTO) {
        KjtQrCodeApplyRespDTO resp = this.execute(reqDTO, KjtServiceType.QRCODE_APPLY,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtQrCodeApplyRespDTO>>() {
                });
        return resp;
    }

    /**
     * 扫码支付进件查询
     * 该接口用于平台方主动查询子商户扫码进件的审核结果状态。
     * 目前是平台方进件报备，不需要子商户再进件
     *
     * @param reqDTO
     * @return
     */
    @Deprecated
    public KjtQrCodeApplyQueryRespDTO qrCodeApplyQuery(KjtQrCodeApplyQueryReqDTO reqDTO) {
        KjtQrCodeApplyQueryRespDTO resp = this.execute(reqDTO, KjtServiceType.QRCODE_APPLY_QUERY,
                new ParameterizedTypeReference<KjtBaseResponseDTO<KjtQrCodeApplyQueryRespDTO>>() {
                });
        return resp;
    }

    /**
     * 调用快捷通网关
     *
     * @param bizObject
     * @param serviceType
     * @param paramTypeRef
     * @param <T>
     * @return
     */
    public <T> T execute(Object bizObject, KjtServiceType serviceType, ParameterizedTypeReference<KjtBaseResponseDTO<T>> paramTypeRef) {
        KjtBaseRequestDTO baseRequest = this.buildBaseRequest(bizObject, serviceType);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        JSONObject jsonObject = JSONObject.parseObject(this.toJsonStringUseJackson(baseRequest));
        log.info("请求参数:{}",JSONUtil.parseObj(bizObject).toStringPretty());
        log.info("请求参数:{}",JSONUtil.parseObj(jsonObject).toStringPretty());
        // value使用urlEncode编码
        jsonObject.forEach((k, v) -> {
            try {
                map.add(k, URLEncoder.encode(v.toString(), "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                log.error("urlEncode异常: ", e);
                throw new BusinessException(Code.ERROR, "请求快捷通失败，url encode异常");
            }
        });
        HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(map, headers);

        log.info("requestNo: {}请求快捷通支付，url: {}, httpEntity: {}, ", baseRequest.getRequestNo(), kjtProperties.getGatewayUrl(), httpEntity);
        ResponseEntity<KjtBaseResponseDTO<T>> responseEntity =
                restTemplate.exchange(kjtProperties.getGatewayUrl(), HttpMethod.POST, httpEntity, paramTypeRef);
        this.saveRequest(baseRequest, serviceType);
        if (responseEntity.getStatusCode() != HttpStatus.OK) {
            log.info("请求快捷通支付，url: {}, requestNo: {}, 失败，http status code: {}", kjtProperties.getGatewayUrl(), baseRequest.getRequestNo(), responseEntity.getStatusCode());
            throw new BusinessException(Code.ERROR, "请求快捷通失败");
        }
        KjtBaseResponseDTO<T> baseResponseDTO = responseEntity.getBody();
        this.saveResponse(baseResponseDTO, baseRequest.getRequestNo());
        log.info("请求结果:{}",JSONUtil.parseObj(baseResponseDTO).toStringPretty());
        if (!StringUtils.equals(baseResponseDTO.getCode(), "S10000")) {
            log.info("请求快捷通支付，url: {}, requestNo: {}, 失败，快捷通状态码: {}，快捷通状态信息：{}, 快捷通子状态码: {}，快捷通子状态信息：{}",
                    kjtProperties.getGatewayUrl(), baseRequest.getRequestNo(), baseResponseDTO.getCode(), baseResponseDTO.getMsg(), baseResponseDTO.getSubCode(), baseResponseDTO.getSubMsg());
            throw new BusinessException(Code.ERROR, "请求快捷通失败");
        }
        // 验签
        this.verifySign(baseResponseDTO, baseRequest.getRequestNo());

        log.info("请求快捷通支付，url: {}, requestNo: {}, 成功, 返回数据：{}", kjtProperties.getGatewayUrl(), baseRequest.getRequestNo(), this.toJsonStringUseJackson(baseResponseDTO));
        return baseResponseDTO.getBizContent();
    }

    /**
     * 验签
     *
     * @param baseResponseDTO
     * @param requestNo
     * @param <T>
     */
    private <T> void verifySign(KjtBaseResponseDTO<T> baseResponseDTO, String requestNo) {
        ResponseParameter<String> copy = new ResponseParameter<>();
        BeanUtil.copyProperties(baseResponseDTO, copy);
        copy.setSign(null);
        copy.setSignType(null);
        copy.setBizContent(toJsonStringUseJackson(baseResponseDTO.getBizContent()));
        VerifyResult verifyResult = securityService.verify(copy, baseResponseDTO.getSign(), baseResponseDTO.getCharset());
        if (!verifyResult.isSuccess()) {
            log.info("请求快捷通支付，url: {}, requestNo: {}, 验签失败，返回数据：{}, 验签结果: {}", kjtProperties.getGatewayUrl(),
                    requestNo, JSONUtil.toJsonStr(baseResponseDTO), verifyResult);
            throw new BusinessException(Code.ERROR, "请求快捷通失败");
        }
    }

    /**
     * 保存响应
     *
     * @param baseResponseDTO
     * @param requestNo
     * @param <T>
     */
    private <T> void saveResponse(KjtBaseResponseDTO<T> baseResponseDTO, String requestNo) {
        KjtInvokeHisDTO invokeHisDTO = new KjtInvokeHisDTO();
        invokeHisDTO.setRequestNo(requestNo);
        invokeHisDTO.setResultCode(baseResponseDTO.getCode());
        invokeHisDTO.setResultMsg(baseResponseDTO.getMsg());
        invokeHisDTO.setResultSubCode(baseResponseDTO.getSubCode());
        invokeHisDTO.setResultSubMsg(baseResponseDTO.getSubMsg());
        invokeHisDTO.setResultSign(baseResponseDTO.getSign());
        if (null != baseResponseDTO.getBizContent()) {
            invokeHisDTO.setResultBizContent(this.toJsonStringUseJackson(baseResponseDTO.getBizContent()));
        }
        invokeHisService.modify(invokeHisDTO);
    }

    /**
     * 保存请求
     *
     * @param baseRequestDTO
     * @param serviceType
     */
    private void saveRequest(KjtBaseRequestDTO baseRequestDTO, KjtServiceType serviceType) {
        KjtInvokeHisDTO invokeHisDTO = new KjtInvokeHisDTO();
        BeanUtil.copyProperties(baseRequestDTO, invokeHisDTO, "timestamp", "bizContent");
        invokeHisDTO.setServiceType(serviceType);
        invokeHisDTO.setTimestamp(baseRequestDTO.getTimestampObj());
        invokeHisDTO.setBizContent(baseRequestDTO.getOriginBizContent());
        invokeHisDTO.setEncryptedBizContent(baseRequestDTO.getBizContent());
        invokeHisService.create(invokeHisDTO);
    }

    /**
     * 构建基础请求对象
     *
     * @param serviceType
     * @return
     */
    private KjtBaseRequestDTO buildBaseRequest(Object bizObject, KjtServiceType serviceType) {
        KjtBaseRequestDTO baseRequest = new KjtBaseRequestDTO();
        baseRequest.setRequestNo(Long.toString(snowflake.nextId()));
        baseRequest.setService(serviceType.getName());
        baseRequest.setVersion(serviceType.getVersion());
        baseRequest.setPartnerId(kjtProperties.getGandaoMerchantId());
        baseRequest.setCharset("UTF-8");
        baseRequest.setFormat("JSON");// 固定
        LocalDateTime now = LocalDateTime.now();
        baseRequest.setTimestamp(DateUtil.DATE_TIME_FORMATTER.format(now));
        baseRequest.setTimestampObj(now);

        String bizContentJson = this.toJsonStringUseJackson(bizObject);
        log.info("requestNo: {}请求快捷通支付，bizContent: {}", baseRequest.getRequestNo(), bizContentJson);
        baseRequest.setOriginBizContent(bizContentJson);
        baseRequest.setBizContent(securityService.encrypt(bizContentJson, baseRequest.getCharset()));
        // sign、sign_type这两个字段不参与签名运算
        RequestBase requestBase = new RequestBase();
        BeanUtil.copyProperties(baseRequest, requestBase);
        baseRequest.setSign(securityService.sign(requestBase, baseRequest.getCharset()));
        baseRequest.setSignType("RSA");
        return baseRequest;
    }

    /**
     * 上传多个影印件
     *
     * @param attachId
     * @param subjectId
     * @param attachType
     * @return
     */
    public String uploadPhotoCopy(String attachId, String subjectId, AttachType attachType) {
        attachmentService.relateAttachment(subjectId, attachId, attachType);
        return this.doUpload(attachId, kjtProperties.getSftp().getPhotoCopyPath());
    }

    /**
     * 上传影印件
     *
     * @param attachIds
     * @param subjectId
     * @param attachType
     * @return
     */
    public List<String> uploadPhotoCopy(List<String> attachIds, String subjectId, AttachType attachType) {
        attachmentService.relateAttachment(subjectId, attachIds, attachType);
        return attachIds.stream().map(attachId -> this.doUpload(attachId, kjtProperties.getSftp().getPhotoCopyPath())).collect(Collectors.toList());
    }

    /**
     * 删除影印件
     *
     * @param subjectId
     * @param attachType
     * @return
     */
    public void deletePhotoCopy(String subjectId, AttachType attachType) {
        List<AttachVO> attachments = attachmentService.getAttachment(subjectId, attachType);
        for (AttachVO attachment : attachments) {
            this.doDelete(attachment.getId(), kjtProperties.getSftp().getPhotoCopyPath());
        }
        attachmentService.deleteAttachment(subjectId, attachType);
    }

    /**
     * 上传对账单
     *
     * @param attachId
     * @param subjectId
     * @param attachType
     * @return
     */
    public String uploadStatement(String attachId, String subjectId, AttachType attachType) {
        attachmentService.relateAttachment(subjectId, attachId, attachType);
        return this.doUpload(attachId, kjtProperties.getSftp().getStatementPath());
    }

    private String doUpload(String attachId, String path) {
        AttachmentEntity attach = attachmentService.getAttach(attachId);
        String objName = attach.getObjName();
        OSSObject ossObject = aliyunOSSUtil.getObject(attach.getBucketName(), objName);
        String suffix = objName.substring(objName.lastIndexOf("."));
        String file = path + "/" + attachId + suffix;
        ChannelSftp client = getSftp().getClient();
        try (InputStream inputStream = ossObject.getObjectContent()) {
            log.info("上传文件到快捷通sftp。srcFile: {}, dest: {}", objName, file);
            client.put(inputStream, file);
            return file;
        } catch (Exception e) {
            log.error("上传文件到快捷通sftp异常。attachId: {}, file:{}, error: ", attach, file, e);
            throw new BusinessException(Code.ERROR, "上传文件到快捷通sftp异常");
        } finally {
            // 直接关闭Sftp之后再创建的话会报错,所以这里手动关channel
            JschUtil.close(client);
        }
    }

    private void doDelete(String attachId, String path) {
        AttachmentEntity attach = attachmentService.getAttach(attachId);
        String objName = attach.getObjName();
        String suffix = objName.substring(objName.lastIndexOf("."));
        String file = path + "/" + attachId + suffix;
        ChannelSftp client = getSftp().getClient();
        try {
            log.info("从快捷通sftp删除文件。srcFile: {}, dest: {}", objName, file);
            client.rm(file);
        } catch (Exception e) {
            log.error("从快捷通sftp删除文件异常。attachId: {}, file:{}, error: ", attach, file, e);
            throw new BusinessException(Code.ERROR, "从快捷通sftp删除文件");
        } finally {
            // 直接关闭Sftp之后再创建的话会报错,所以这里手动关channel
            JschUtil.close(client);
        }
    }

    protected Sftp getSftp() {
        return new Sftp(kjtProperties.getSftp().getHost(), kjtProperties.getSftp().getPort(),
                kjtProperties.getSftp().getUsername(), kjtProperties.getSftp().getPassword());
    }

    @SneakyThrows
    private String toJsonStringUseJackson(Object object) {
        return objectMapper.writeValueAsString(object);
    }

}
